#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAY_SIZE(xs) (sizeof(xs) / sizeof((xs)[0]))
#define BM_STACK_CAPACITY 1024 * 10000 // stack capacity
#define BM_PROGRAM_CAPACITY 1024
#define BM_EXECUTION_LIMIT 69

typedef enum {
  TRAP_OK,                  // ok
  TRAP_STACK_OVERFLOW,      // overflow
  TRAP_STACK_UNDERFLOW,     // underflow
  TRAP_ILLEGAL_INST,        // illegal instruction
  TRAP_DIV_BY_ZERO,         // div by 0
  TRAP_ILLEGAL_INST_ACCESS, // illegal instruction access
} Trap;                     // is for exception, like error code

const char *trap_as_cstr(Trap trap) {
  switch (trap) {
  case TRAP_OK:
    return "Trap ok";
  case TRAP_STACK_OVERFLOW:
    return "Trap stack Overflow";
  case TRAP_STACK_UNDERFLOW:
    return "Trap stack Underflow";
  case TRAP_ILLEGAL_INST:
    return "Trap illegal instruction";
  case TRAP_DIV_BY_ZERO:
    return "Trap div by zero";
  case TRAP_ILLEGAL_INST_ACCESS:
    return "Trap illegal instruction access";
  default:
    assert(0 && "trap_as_cstr(): Unreachable");
    return "(Unknown Trap)";
  }
}

typedef int64_t Word; // word aka int64

typedef enum {
  INST_PUSH,
  INST_PLUS,
  INST_MINUS,
  INST_MULT,
  INST_DIV,
  INST_JMP,
  INST_HALT,
  INST_JMP_IF,
} Inst_Type; // for stack usage, instruction type;

const char *inst_type_as_cstr(Inst_Type type) {
  switch (type) {
  case INST_PUSH:
    return "INST_PUSH";
  case INST_PLUS:
    return "INST_PLUS";
  case INST_MINUS:
    return "INST_MINUS";
  case INST_MULT:
    return "INST_MULT";
  case INST_DIV:
    return "INST_DIV";
  case INST_JMP:
    return "INST_JMP";
  case INST_HALT:
    return "INST_HALT";
  default:
    assert(0 && "inst_type_as_cstr(): Unreachable");
  }
}

typedef struct {
  Inst_Type type;
  Word operand;
} Inst; // name as like instruction;

// macro -----------------
#define Make_Inst_Push(value) ((Inst){.type = INST_PUSH, .operand = (value)})

#define Make_Inst_Plus ((Inst){.type = INST_PLUS})

#define Make_Inst_Minus ((Inst){.type = INST_MINUS})

#define Make_Inst_Mult ((Inst){.type = INST_MULT})

#define Make_Inst_Div ((Inst){.type = INST_DIV})

#define Make_Inst_Jmp(addr)                                                    \
  {                                                                            \
    (Inst) { .type = INST_JMP, .operand = (addr) }                             \
  }

#define Make_Inst_Halt                                                         \
  {                                                                            \
    (Inst) { .type = INST_HALT }                                               \
  }
// macro end ------------------------

typedef struct {
  Word stack[BM_STACK_CAPACITY];
  Word stack_size;

  Inst program[BM_PROGRAM_CAPACITY];
  Word program_size;

  Word ip;
  int halt;
} Bm;

Bm bm = {0}; // zero init;

// push instruction to virtual machine
void bm_push_inst(Bm *bm, Inst inst) {
  assert(bm->program_size < BM_PROGRAM_CAPACITY);
  bm->program[bm->program_size++] = inst;
}

// run instruction
Trap bm_execute_inst(Bm *bm) {
  if (bm->ip < 0 || bm->ip >= bm->program_size) {
    return TRAP_ILLEGAL_INST_ACCESS;
  }

  Inst inst = bm->program[bm->ip];
  switch (inst.type) {
  case INST_PUSH:
    if (bm->stack_size >= BM_STACK_CAPACITY) {
      return TRAP_STACK_OVERFLOW;
    }
    bm->stack[bm->stack_size++] = inst.operand;
    bm->ip += 1;
    break;
  case INST_PLUS:
    if (bm->stack_size < 2) {
      return TRAP_STACK_UNDERFLOW;
    }
    bm->stack[bm->stack_size - 2] += bm->stack[bm->stack_size - 1];
    bm->stack_size -= 1;
    bm->ip += 1;
    break;
  case INST_MINUS:
    if (bm->stack_size < 2) {
      return TRAP_STACK_UNDERFLOW;
    }
    bm->stack[bm->stack_size - 2] -= bm->stack[bm->stack_size - 1];
    bm->stack_size -= 1;
    bm->ip += 1;
    break;
  case INST_MULT:
    if (bm->stack_size < 2) {
      return TRAP_STACK_UNDERFLOW;
    }
    bm->stack[bm->stack_size - 2] *= bm->stack[bm->stack_size - 1];
    bm->stack_size -= 1;
    bm->ip += 1;
    break;
  case INST_DIV:
    if (bm->stack_size < 2) {
      return TRAP_STACK_UNDERFLOW;
    }
    if (bm->stack[bm->stack_size - 1] == 0) {
      return TRAP_DIV_BY_ZERO;
    }
    bm->stack[bm->stack_size - 2] /= bm->stack[bm->stack_size - 1];
    bm->stack_size -= 1;
    bm->ip += 1;
    break;
  case INST_JMP:
    bm->ip = inst.operand;
    break;
  case INST_HALT:
    bm->halt = 1; // halt = true
    break;
  default:
    return TRAP_ILLEGAL_INST;
  }

  return TRAP_OK;
}

void bm_dump_stack(FILE *stream, const Bm *bm) {
  fprintf(stream, "Stack:\n");
  if (bm->stack_size > 0) {
    for (size_t i = 0; i < bm->stack_size; ++i) {
      fprintf(stream, "\t%ld\n", bm->stack[i]);
    }
  } else {
    fprintf(stream, "\t[empty]\n");
  }
}

Inst program[] = {Make_Inst_Push(69), Make_Inst_Push(420), Make_Inst_Plus,
                  Make_Inst_Push(42), Make_Inst_Minus,     Make_Inst_Push(2),
                  Make_Inst_Mult,     Make_Inst_Push(10),  Make_Inst_Div};

void bm_load_program_from_memory(Bm *bm, Inst *program, size_t programsize) {
  assert(programsize < BM_PROGRAM_CAPACITY);
  memcpy(bm->program, program, sizeof(program[0]) * programsize);
  bm->program_size = programsize;
}

int main(int argc, char *argv[]) {
  bm_load_program_from_memory(&bm, program, ARRAY_SIZE(program));

  bm_dump_stack(stdout, &bm);
  while (!bm.halt) {
    printf("%s\n", inst_type_as_cstr(program[bm.ip].type));
    Trap trap_result = bm_execute_inst(&bm);
    bm_dump_stack(stdout, &bm);
    if (trap_result != TRAP_OK) {
      fprintf(stderr, "Trap activated: %s\n", trap_as_cstr(trap_result));
      exit(1);
    }
  }

  return 0;
}
